/*
 * ========================================================================
 * Copyright 2008 University of Washington
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * ========================================================================
 */

/*
 * Converter for Exchange CalendarItem to iCal4j
 */

package ExchangeEventConverter;

import com.microsoft.schemas.exchange.services._2006.types.AttendeeType;
import com.microsoft.schemas.exchange.services._2006.types.CalendarItemType;
import com.microsoft.schemas.exchange.services._2006.types.CalendarItemTypeType;
import com.microsoft.schemas.exchange.services._2006.types.DayOfWeekIndexType;
import com.microsoft.schemas.exchange.services._2006.types.DayOfWeekType;
import com.microsoft.schemas.exchange.services._2006.types.DeletedOccurrenceInfoType;
import com.microsoft.schemas.exchange.services._2006.types.ItemIdType;
import com.microsoft.schemas.exchange.services._2006.types.RecurrenceType;
import com.microsoft.schemas.exchange.services._2006.types.RelativeMonthlyRecurrencePatternType;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.StringTokenizer;
import java.util.TimeZone;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import net.fortuna.ical4j.model.Calendar;
import net.fortuna.ical4j.model.Date;
import net.fortuna.ical4j.model.DateList;
import net.fortuna.ical4j.model.DateTime;
import net.fortuna.ical4j.model.Property;
import net.fortuna.ical4j.model.Recur;
import net.fortuna.ical4j.model.TimeZoneRegistry;
import net.fortuna.ical4j.model.TimeZoneRegistryFactory;
import net.fortuna.ical4j.model.WeekDay;
import net.fortuna.ical4j.model.component.VEvent;
import net.fortuna.ical4j.model.component.VTimeZone;
import net.fortuna.ical4j.model.parameter.Cn;
import net.fortuna.ical4j.model.parameter.CuType;
import net.fortuna.ical4j.model.parameter.PartStat;
import net.fortuna.ical4j.model.parameter.Value;
import net.fortuna.ical4j.model.property.Attendee;
import net.fortuna.ical4j.model.property.CalScale;
import net.fortuna.ical4j.model.property.Description;
import net.fortuna.ical4j.model.property.ExDate;
import net.fortuna.ical4j.model.property.Location;
import net.fortuna.ical4j.model.property.Organizer;
import net.fortuna.ical4j.model.property.ProdId;
import net.fortuna.ical4j.model.property.RRule;
import net.fortuna.ical4j.model.property.RecurrenceId;
import net.fortuna.ical4j.model.property.Uid;
import net.fortuna.ical4j.model.property.Version;
import org.apache.log4j.Logger;
/**
 *
 * @author nickchen
 */
public class iCal4j {
    
    private static Logger  logger = Logger.getLogger( iCal4j.class );

    private Calendar ical;
    
    public iCal4j(  ) {
        ical   = new Calendar();
        ical.getProperties().add(new ProdId("-//UWExchange ICS Converter//iCal4j 1.0//EN"));
        ical.getProperties().add(Version.VERSION_2_0);
        ical.getProperties().add(CalScale.GREGORIAN);
        ical.getComponents().add( convertToVTimeZone(TimeZone.getDefault()) );
    }
    
    
    public void add( CalendarItemType item ) {
        ical.getComponents().add( convertExchangeCalendarItemToiCal(item) );
    }
    
    @Override
    public String toString() {
        return ical.toString();
    }

    public static RRule convertRecurrence( CalendarItemType item ) {
        RRule   rule    = null;
        Recur recur     = null;
        if( item.getCalendarItemType() != null && item.getCalendarItemType().equals( CalendarItemTypeType.RECURRING_MASTER )) {
            if( item.getRecurrence() != null ) {
                    // exchange recurrences are non-exclusive, ie it is possible to get different recurrence types
                RecurrenceType recurrenceType = item.getRecurrence();
                logger.info("RECR: "+item.getSubject());
                if( recurrenceType.getAbsoluteMonthlyRecurrence() != null ) {
                    logger.info("RECR: getAbsoluteMonthlyRecurrence");
                    recur               = new Recur( Recur.MONTHLY, item.getAdjacentMeetingCount() );
                    recur.setInterval( recurrenceType.getAbsoluteMonthlyRecurrence().getInterval() );
                    if( recurrenceType.getNumberedRecurrence() != null ) {
                        recur.setCount( recurrenceType.getNumberedRecurrence().getNumberOfOccurrences() );
                    }
                }
                
                if( recurrenceType.getAbsoluteYearlyRecurrence() != null ) {
                    logger.info("RECR: getAbsoluteYearlyRecurrence  [not implemented]");
                    
                }
                
                if( recurrenceType.getDailyRecurrence() != null ) {
                    logger.info("RECR: getDailyRecurrence");
                    recur               = new Recur( Recur.DAILY, item.getAdjacentMeetingCount() );
                    recur.setInterval( recurrenceType.getDailyRecurrence().getInterval() );
                }
                
                
                if( recurrenceType.getRelativeMonthlyRecurrence() != null ) {
                    // to do
                    logger.info("RECR: getRelativeMonthlyRecurrence");
                    RelativeMonthlyRecurrencePatternType rrpt = recurrenceType.getRelativeMonthlyRecurrence();
                    recur               = new Recur( Recur.MONTHLY, item.getAdjacentMeetingCount() );
                    if( rrpt.getDayOfWeekIndex().equals( DayOfWeekIndexType.FIRST ) ) {
                        recur.getSetPosList().add( new Integer (1));
                    } else if(  rrpt.getDayOfWeekIndex().equals( DayOfWeekIndexType.SECOND ) ) {
                        recur.getSetPosList().add( new Integer (2));
                    } else if(  rrpt.getDayOfWeekIndex().equals( DayOfWeekIndexType.THIRD ) ) {
                        recur.getSetPosList().add( new Integer (3));
                    } else if(  rrpt.getDayOfWeekIndex().equals( DayOfWeekIndexType.FOURTH ) ) {
                        recur.getSetPosList().add( new Integer (4));
                    } else if(  rrpt.getDayOfWeekIndex().equals( DayOfWeekIndexType.LAST ) ) {
                        recur.getSetPosList().add( new Integer (5));
                    }
                    WeekDay weekDay = convertDayOfWeek( rrpt.getDaysOfWeek() );
                    if (weekDay != null) {
                        recur.getDayList().add(weekDay);
                    }
                    recur.setInterval( rrpt.getInterval() );
                }
                if( recurrenceType.getRelativeYearlyRecurrence() != null ) {
                    // to do
                    logger.info("RECR: getRelativeYearlyRecurrence  [not implemented]");
                }
                
                if( recurrenceType.getWeeklyRecurrence() != null ) {
                    logger.info("RECR: getWeeklyRecurrence (interval="+recurrenceType.getWeeklyRecurrence().getInterval()+")");
                    recur               = new Recur( Recur.WEEKLY, item.getAdjacentMeetingCount() );
                    
                    recur.setInterval( recurrenceType.getWeeklyRecurrence().getInterval() );
                    for( DayOfWeekType dayOfWeek : recurrenceType.getWeeklyRecurrence().getDaysOfWeek() ) {
                        WeekDay weekDay = convertDayOfWeek( dayOfWeek );
                        if( weekDay != null ) {
                            recur.getDayList().add( weekDay );
                        }
                    }
                }

                if( recur != null && recurrenceType.getEndDateRecurrence() != null ) {
                    DateTime endTime = new DateTime(recurrenceType.getEndDateRecurrence().getEndDate().toGregorianCalendar().getTime());
                    java.util.GregorianCalendar cal = new java.util.GregorianCalendar();
                    cal.setTime(endTime);
                    cal.add(java.util.GregorianCalendar.DATE, 1);  // add 1 day to the end time to include the last day
                    endTime = new DateTime(cal.getTime());
                    recur.setUntil( endTime );
                }
                
                if( recur != null && recurrenceType.getNoEndRecurrence() != null ) {
                    logger.info("RECR: getNoEndRecurrence");
                    recur.setUntil(null);
                }
                
                if( recur != null && recurrenceType.getNumberedRecurrence() != null ) {
                    logger.info("RECR: getNumberedRecurrence");
                    recur.setCount( recurrenceType.getNumberedRecurrence().getNumberOfOccurrences() );
                }

            }
        }
        
        if( recur != null ) {
            rule = new RRule( recur );
        }
        
        return rule;
    }

    public static WeekDay convertDayOfWeek( DayOfWeekType dayOfWeek ) {
        if (dayOfWeek.equals(DayOfWeekType.MONDAY)) {
            return WeekDay.MO;
        } else if (dayOfWeek.equals(DayOfWeekType.TUESDAY)) {
            return WeekDay.TU;
        } else if (dayOfWeek.equals(DayOfWeekType.WEDNESDAY)) {
            return WeekDay.WE;
        } else if (dayOfWeek.equals(DayOfWeekType.THURSDAY)) {
            return WeekDay.TH;
        } else if (dayOfWeek.equals(DayOfWeekType.FRIDAY) ) {
            return WeekDay.FR;
        } else if( dayOfWeek.equals(DayOfWeekType.SATURDAY) ) {
            return WeekDay.SA;
        } else if( dayOfWeek.equals(DayOfWeekType.SUNDAY) ) {
            return WeekDay.SU;
        }
        return null;
    }

    public static VEvent convertExchangeCalendarItemToiCal( CalendarItemType item ) {
        VEvent event = null;
        TimeZoneRegistry registry = TimeZoneRegistryFactory.getInstance().createRegistry();
        TimeZone timezone = registry.getTimeZone( TimeZone.getDefault().getID() );

        logger.debug("processing CalendarItemType: "+item.getSubject());
        if( item.isIsAllDayEvent() ) {
            Date start      = null;
            Date end        = null;
            if (item.getStart() != null) {
                start       = new Date(item.getStart().toGregorianCalendar().getTime());
            }
            if (item.getEnd() != null) {
                end         = new Date(item.getEnd().toGregorianCalendar().getTime());
            }
            event           = new VEvent(start, end, item.getSubject());
            event.getProperties().getProperty( Property.DTSTART ).getParameters().add( Value.DATE );
        } else {
            DateTime start  = null;
            DateTime end    = null;

/*
 * To use the item timezone from Exchange I need to somehow parse the
 * item.getTimeZone() String into a TimeZone ID
 * For now all I can do is use the system timezone or no timezone
 */
/*           String tzStr;
            if ( item.getTimeZone().substring(0, 1).equals("(") ) {
                tzStr = item.getTimeZone().substring(1, 7);
            }
            else {
                tzStr = item.getTimeZone();
            }
            TimeZone t = java.util.SimpleTimeZone.getTimeZone( tzStr );
            System.out.println( timezone.getID() +" == "+ t.getID() + " <-- " + tzStr + " <-- " + item.getTimeZone() );
*///////////////

            if (item.getStart() != null) {
                start       = new DateTime(item.getStart().toGregorianCalendar().getTime());
                start.setTimeZone((net.fortuna.ical4j.model.TimeZone) timezone);
            }
            if (item.getEnd() != null) {
                end         = new DateTime(item.getEnd().toGregorianCalendar().getTime());
                end.setTimeZone((net.fortuna.ical4j.model.TimeZone) timezone);
            }
            event           = new VEvent(start, end, item.getSubject());
        }

        RRule rrule = convertRecurrence( item );

        if( rrule != null )
            event.getProperties().add( rrule );

        Uid uid = convertUid( item );
        if( uid != null )
            event.getProperties().add(uid);

        if( item.getLocation() != null )
            event.getProperties().add( new Location( item.getLocation() ) );
        
        if( item.getBody() != null )
            event.getProperties().add( new Description( convertHtmlToText( item.getBody().getValue() )) );
        
        if( item.getOrganizer() != null) {
            try {
                String name = item.getOrganizer().getMailbox().getName();
                String email = item.getOrganizer().getMailbox().getEmailAddress();
                Organizer organizer = new Organizer();
                if ( email != null ) {
                    organizer.setValue("mailto:" + email);
                }
                organizer.getParameters().add(new Cn(name));
                event.getProperties().add(organizer);
            } catch (URISyntaxException ex) {
                logger.error("event organizer: " + ex);
            }
        }
        
        if ( item.getResources() != null ) {
            logger.debug("resource attendees");
            for (AttendeeType attendee : item.getResources().getAttendee() ) {
                // probably need to do somethine with these too
                Attendee participant = convertAttendee( attendee );

                // to get the real CuType requires an extra SOAP request so this is just a hack
                // should use GetUserAvailabilityRequest to retrieve getAttendeeType() and see if it is "ROOM"
                participant.getParameters().add( new CuType( "ROOM" ) );
                
                event.getProperties().add(participant);
                //ATTENDEE;CUTYPE=GROUP:MAILTO:ietf-calsch@imc.org
            }
        }

        if ( item.getOptionalAttendees() != null ) {
            for (AttendeeType attendee : item.getOptionalAttendees().getAttendee() ) {
                Attendee participant = convertAttendee( attendee );
                if (participant != null) {
                    event.getProperties().add(participant);
                }
            }
        }
        
        if ( item.getRequiredAttendees() != null ) {
            for (AttendeeType attendee : item.getRequiredAttendees().getAttendee() ) {
                Attendee participant = convertAttendee( attendee );
                if (participant != null) {
                    event.getProperties().add(participant);
                }
            }
        }

        if( item.getCalendarItemType().equals( CalendarItemTypeType.EXCEPTION) ) {
            DateTime originalStart = new DateTime( item.getOriginalStart().toGregorianCalendar().getTime() );
            originalStart.setTimeZone((net.fortuna.ical4j.model.TimeZone) timezone);
            event.getProperties().add( new RecurrenceId( originalStart ));
        }
        
        if( item.getCalendarItemType().equals( CalendarItemTypeType.RECURRING_MASTER) && item.getDeletedOccurrences() != null ) {
            DateList dateList = new DateList();
            for( DeletedOccurrenceInfoType doit : item.getDeletedOccurrences().getDeletedOccurrence()) {
                DateTime deletedOccurrence = new DateTime( doit.getStart().toGregorianCalendar().getTime() );
                dateList.add(deletedOccurrence);
            }
            event.getProperties().add( new ExDate(dateList) );
        }
        return event;
    }
    
    public static String convertHtmlToText( String html ) {
        String convertedString = html;
        convertedString = convertedString.replaceAll("<.*?>", "");  // strip all HTML tags
        convertedString = convertedString.replaceAll("&nbsp;", " ");  // convert &nbsp;
        convertedString = convertedString.replaceAll("&amp;", "&");  // convert &amp;
        convertedString = convertedString.trim(); // strip leading and trailing whitespace
        convertedString = convertedString.replaceAll("\n{2,}", "\n"); // collapse multipe empty lines
        return convertedString;
    }
    
    public static Attendee convertAttendee( AttendeeType attendee ) {
        Attendee icalAttendee = null;
        try {
            icalAttendee = new Attendee();
            String name = attendee.getMailbox().getName();
            String email = attendee.getMailbox().getEmailAddress();
            String response = attendee.getResponseType().value();

            if ( response.equals("Unknown") ) {
                response = null;
            } else if ( response.equals("Accept") ) {
                response = "ACCEPTED";
            } else if ( response.equals("Decline") ) {
                response = "DECLINED";
            } else if ( response.equals("Tentative") ) {
                response = "TENTATIVE";
            } else if ( response.equals("Organizer") ) {
                response = "ACCEPTED";
            } else if ( response.equals("NoResponseReceived") ) {
                response = null;
            }
//            if ( email != null ) {
            if ( email != null && validateEmail(email) ) {
                icalAttendee.setValue("mailto:" + email);
            }
            icalAttendee.getParameters().add( new Cn(name) );
            if ( response != null ) {
                icalAttendee.getParameters().add( new PartStat(response) );
            }
        } catch (URISyntaxException ex) {
            logger.error("convertAttendee: " + ex);
        }
        return icalAttendee;
    }
    
    public static VTimeZone convertToVTimeZone( TimeZone tz ) {
        TimeZoneRegistry registry = TimeZoneRegistryFactory.getInstance().createRegistry();
        net.fortuna.ical4j.model.TimeZone timezone = registry.getTimeZone( tz.getID() );
        return timezone.getVTimeZone();
    }

    public static Uid convertUid( CalendarItemType item ) {
        Uid uid = null;
        if( item.getUID() != null ) {
            uid = new Uid( item.getUID() );
        } else if( item.getItemId() != null ) {
            ItemIdType id = item.getItemId();
            uid = new Uid( id.getChangeKey() );
        }
        return uid;
    }
    
    public static String convertCalendarItemType( ArrayList<CalendarItemType> calendarItemType ) {
        iCal4j ical4j = new iCal4j();
        
        for( CalendarItemType item : calendarItemType ) {
            ical4j.add(item);
        }
        return ical4j.toString();
    }

    public static boolean validateEmail( String email ){
         Pattern p = Pattern.compile(".+@.+\\.[a-z]+");
         Matcher m = p.matcher(email);
         boolean matchFound = m.matches();
         StringTokenizer st = new StringTokenizer(email, ".");
         String lastToken = null;
         while (st.hasMoreTokens()) {
             lastToken = st.nextToken();
         }
         if (matchFound && lastToken.length() >= 2
             && email.length() - 1 != lastToken.length()) {
             return true;
         }
         else return false;
     }

}

